package wsepForum.domain;

import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;
import java.util.Vector;

import wsepForum.domain.exceptions.NoSuchUserSessionExcepion;
import wsepForum.logging.ForumLogger;
import wsepForum.persistance.IPersistentHandler;

public class SessionManager {
	/*****************************
	 * MEMBERS                   *
	 *****************************/

	public static SessionManager INSTANCE = null;

	/**
	 * holds all the connection sessions of the client
	 */
	private Hashtable<Double,Session> sessionMap;

	/**
	 * holds all the session of each loged-in user
	 */
	private Hashtable<String,Vector<Session>> memberSessionMap;

	/**
	 * in use of generateSID function
	 */
	private double lastSID;

	/**
	 * The interface supplied by the persistent layer.
	 */
	private IPersistentHandler persistent;


	/*****************************
	 * METHODS                   *
	 *****************************/
	public static synchronized SessionManager getInstance() {
		if(INSTANCE == null) {
			INSTANCE = new SessionManager();
			INSTANCE.sessionMap = new Hashtable<Double, Session>();
			INSTANCE.memberSessionMap = new Hashtable<String, Vector<Session>>();
			INSTANCE.lastSID = 0;
		}
		return INSTANCE;
	}

	public void setPersistent(IPersistentHandler persistent) {
		this.persistent = persistent;
	}

	/**
	 * Creates new session and add it to map
	 * @return the new session
	 */
	public Session createNewSession() {
		double sid = generateSID();
		Session session = new Session(sid,this.persistent);
		this.sessionMap.put(sid, session);
		ForumLogger.getInstance().info("SessionManager created new session with SID="+sid, ForumLogger.LoggerCategory.DOMAIN);
		return session;
	}

	public void endSession(Double sid){
		this.sessionMap.remove(sid);
		ForumLogger.getInstance().info("SessionManager removed session with SID="+sid, ForumLogger.LoggerCategory.DOMAIN);
	}

	public synchronized boolean addUserSession(String userName,Session session){
		boolean notContains = true;
		Vector<Session> userSessions = new Vector<Session>();
		if(this.memberSessionMap.containsKey(userName)){
			ForumLogger.getInstance().info("SessionManager added session to user " + userName, ForumLogger.LoggerCategory.DOMAIN);
			notContains = false;
			userSessions = this.memberSessionMap.get(userName);
		}
		else {
			ForumLogger.getInstance().info("SessionManager added user " + userName + " to user session table", ForumLogger.LoggerCategory.DOMAIN);
		}
		userSessions.add(session);
		this.memberSessionMap.put(userName, userSessions);
		return notContains;
	}

	public synchronized boolean removeUserSession(String userName,Session session) throws NoSuchUserSessionExcepion{
		boolean wasRemoved = false;
		if(this.memberSessionMap.containsKey(userName)){
			Vector<Session> userSessions = this.memberSessionMap.get(userName);
			if(userSessions.size() == 1){
				wasRemoved = true;
				this.memberSessionMap.remove(userName);
				ForumLogger.getInstance().info("SessionManager removed user " + userName + " from user session table", ForumLogger.LoggerCategory.DOMAIN);
			}
			else {
				userSessions.remove(session);
				this.memberSessionMap.put(userName, userSessions);
				ForumLogger.getInstance().info("SessionManager removed a session from user " + userName + " on user session table", ForumLogger.LoggerCategory.DOMAIN);
			}
		}
		else throw new NoSuchUserSessionExcepion();

		return wasRemoved;
	}

	public synchronized int getCurrentOnline(){
		int memberSessionsCount = 0;
		Set<String> memberConnected = new HashSet<String>(this.memberSessionMap.keySet());
		for (String usrName : memberConnected) {
			memberSessionsCount += this.memberSessionMap.get(usrName).size();
		}
		//System.err.println("this.sessionMap.size(): " + this.sessionMap.size() + " memberSessionsCount: " + memberSessionsCount + " this.memberSessionMap.size(): " + this.memberSessionMap.size());
		return this.sessionMap.size() - memberSessionsCount + this.memberSessionMap.size();
	}

	/**
	 * Generates new session id
	 * @return new session id as integer
	 */
	private synchronized double generateSID() {
		return ++this.lastSID;
	}

	/** // TODO TEST IT
	 * Kills an existing session
	 * @param sid the session's id
	 */
	public void killSession(double sid) {
		ForumLogger.getInstance().info("SessionManager removed a session with sid " + sid, ForumLogger.LoggerCategory.DOMAIN);
		this.sessionMap.remove(sid);
	}

	/**
	 * @return the sessionMap
	 */
	public Hashtable<Double, Session> getSessionMap() {
		return this.sessionMap;
	}

	/**
	 * @param sessionMap the sessionMap to set
	 */
	public void setSessionMap(Hashtable<Double, Session> sessionMap) {
		this.sessionMap = sessionMap;
	}

	public synchronized void notifyUserChange(String userName){
		if(this.memberSessionMap.containsKey(userName)){
			Vector<Session> userSessions = new Vector<Session>(this.memberSessionMap.get(userName));

			for (Session session : userSessions) {
				for (ISessionObserver disconnector : session.getSessionObserverVector()) {
					disconnector.onLogoutDemand();
				}
			}
		}
	}

}
